/*
	ONEGAME
    Copyright (C) 2009  Pablo Caballero

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package swing.tree;

import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreePath;

/**
 * Abstract tree model that supports generic dynamically changing TreeModels.
 * @version 1.0 19-06-2009
 */
public abstract class AbstractTreeModel implements javax.swing.tree.TreeModel {

	protected javax.swing.event.EventListenerList listeners;

	/**
	 * Constructor
	 */
	protected AbstractTreeModel() {
		listeners = new javax.swing.event.EventListenerList();
	}

	/**
	 * Returns the index of child in parent.  If either {@code parent}
	 * or {@code child} is {@code null}, returns -1.
	 * If either {@code parent} or {@code child} don't
	 * belong to this tree model, returns -1.
	 *
	 * @param parent a node in the tree, obtained from this data source
	 * @param child the node we are interested in
	 * @return the index of the child in the parent, or -1 if either
	 *    {@code child} or {@code parent} are {@code null}
	 *    or don't belong to this tree model
	 */
	public int getIndexOfChild(Object parent, Object child) {
		for (int count = getChildCount(parent), i = 0; i < count; i++) {
			if (getChild(parent, i).equals(child)) {
				return i;
			}
		}
		return -1;
	}

	/** Call when there is a new root, which may be null, i.e. not existent. */
	protected void fireNewRoot() {
		Object[] pairs = listeners.getListenerList();
		Object root = getRoot();

		/* Undocumented. I think it is the only reasonable/possible solution
		to use use null as path if there is no root. TreeModels without
		root aren't important anyway, since JTree doesn't support them (yet).
		 */
		javax.swing.tree.TreePath path = (root != null) ? new javax.swing.tree.TreePath(root) : null;
		javax.swing.event.TreeModelEvent e = null;

		for (int i = pairs.length - 2; i >= 0; i -= 2) {
			if (pairs[i] == javax.swing.event.TreeModelListener.class) {
				if (e == null) {
					e = new javax.swing.event.TreeModelEvent(this, path, null, null);
				}
				((javax.swing.event.TreeModelListener) pairs[i + 1]).treeStructureChanged(e);
			}
		}
	}

	/** Call when everything but the root has changed. Only may be called
	 * when the root is not null. Otherwise there isn't a structure to have
	 * changed.
	 */
	protected void fireStructureChanged() {
		if (getRoot() != null) {
			fireTreeStructureChanged(new javax.swing.tree.TreePath(getRoot()));
		}
	}

	/** Call when a node has changed its leaf state. */
	protected void firePathLeafStateChanged(javax.swing.tree.TreePath path) {
		fireTreeStructureChanged(path);
	}

	/** Call when the tree structure below the path has completely changed. */
	protected void fireTreeStructureChanged(javax.swing.tree.TreePath parentPath) {
		Object[] pairs = listeners.getListenerList();

		javax.swing.event.TreeModelEvent e = null;
		for (int i = pairs.length - 2; i >= 0; i -= 2) {
			if (pairs[i] == javax.swing.event.TreeModelListener.class) {
				if (e == null) {
					e = new javax.swing.event.TreeModelEvent(this, parentPath, null, null);
				}
				((javax.swing.event.TreeModelListener) pairs[i + 1]).treeStructureChanged(e);
			}
		}
	}

	/** Call when the path itself has changed, but no structure changes
	 * have occurred.
	 */
	protected void firePathChanged(javax.swing.tree.TreePath path) {
		Object node = path.getLastPathComponent();
		javax.swing.tree.TreePath parentPath = path.getParentPath();

		if (parentPath == null) {
			fireChildrenChanged(path, null, null);
		} else {
			Object parent = parentPath.getLastPathComponent();
			fireChildChanged(parentPath, getIndexOfChild(parent, node), node);
		}
	}

	protected void fireChildAdded(javax.swing.tree.TreePath parentPath, int index, Object child) {
		fireChildrenAdded(parentPath, new int[]{index}, new Object[]{child});
	}

	protected void fireChildChanged(javax.swing.tree.TreePath parentPath, int index, Object child) {
		fireChildrenChanged(parentPath, new int[]{index}, new Object[]{child});
	}

	protected void fireChildRemoved(javax.swing.tree.TreePath parentPath, int index, Object child) {
		fireChildrenRemoved(parentPath, new int[]{index}, new Object[]{child});
	}

	protected void fireChildrenAdded(javax.swing.tree.TreePath parentPath, int[] indices, Object[] children) {
		Object[] pairs = listeners.getListenerList();
		javax.swing.event.TreeModelEvent e = null;
		for (int i = pairs.length - 2; i >= 0; i -= 2) {
			if (pairs[i] == javax.swing.event.TreeModelListener.class) {
				if (e == null) {
					e = new javax.swing.event.TreeModelEvent(this, parentPath, indices, children);
				}
				((javax.swing.event.TreeModelListener) pairs[i + 1]).treeNodesInserted(e);
			}
		}
	}

	protected void fireChildrenChanged(javax.swing.tree.TreePath parentPath, int[] indices, Object[] children) {
		Object[] pairs = listeners.getListenerList();
		javax.swing.event.TreeModelEvent e = null;
		for (int i = pairs.length - 2; i >= 0; i -= 2) {
			if (pairs[i] == javax.swing.event.TreeModelListener.class) {
				if (e == null) {
					e = new javax.swing.event.TreeModelEvent(this, parentPath, indices, children);
				}
				((javax.swing.event.TreeModelListener) pairs[i + 1]).treeNodesChanged(e);
			}
		}
	}

	protected void fireChildrenRemoved(javax.swing.tree.TreePath parentPath, int[] indices, Object[] children) {
		Object[] pairs = listeners.getListenerList();
		javax.swing.event.TreeModelEvent e = null;
		for (int i = pairs.length - 2; i >= 0; i -= 2) {
			if (pairs[i] == javax.swing.event.TreeModelListener.class) {
				if (e == null) {
					e = new javax.swing.event.TreeModelEvent(this, parentPath, indices, children);
				}
				((javax.swing.event.TreeModelListener) pairs[i + 1]).treeNodesRemoved(e);
			}
		}
	}

	/**
	 * Creates and returns a copy of this object.  The precise meaning
	 * of "copy" may depend on the class of the object. The general
	 * intent is that, for any object <tt>x</tt>, the expression:
	 * <blockquote>
	 * <pre>
	 * x.clone() != x</pre></blockquote>
	 * will be true, and that the expression:
	 * <blockquote>
	 * <pre>
	 * x.clone().getClass() == x.getClass()</pre></blockquote>
	 * will be <tt>true</tt>, but these are not absolute requirements.
	 * While it is typically the case that:
	 * <blockquote>
	 * <pre>
	 * x.clone().equals(x)</pre></blockquote>
	 * will be <tt>true</tt>, this is not an absolute requirement.
	 * <p>
	 * By convention, the returned object should be obtained by calling
	 * <tt>super.clone</tt>.  If a class and all of its superclasses (except
	 * <tt>Object</tt>) obey this convention, it will be the case that
	 * <tt>x.clone().getClass() == x.getClass()</tt>.
	 * <p>
	 * By convention, the object returned by this method should be independent
	 * of this object (which is being cloned).  To achieve this independence,
	 * it may be necessary to modify one or more fields of the object returned
	 * by <tt>super.clone</tt> before returning it.  Typically, this means
	 * copying any mutable objects that comprise the internal "deep structure"
	 * of the object being cloned and replacing the references to these
	 * objects with references to the copies.  If a class contains only
	 * primitive fields or references to immutable objects, then it is usually
	 * the case that no fields in the object returned by <tt>super.clone</tt>
	 * need to be modified.
	 * <p>
	 * The method <tt>clone</tt> for class <tt>Object</tt> performs a
	 * specific cloning operation. First, if the class of this object does
	 * not implement the interface <tt>Cloneable</tt>, then a
	 * <tt>CloneNotSupportedException</tt> is thrown. Note that all arrays
	 * are considered to implement the interface <tt>Cloneable</tt>.
	 * Otherwise, this method creates a new instance of the class of this
	 * object and initializes all its fields with exactly the contents of
	 * the corresponding fields of this object, as if by assignment; the
	 * contents of the fields are not themselves cloned. Thus, this method
	 * performs a "shallow copy" of this object, not a "deep copy" operation.
	 * <p>
	 * The class <tt>Object</tt> does not itself implement the interface
	 * <tt>Cloneable</tt>, so calling the <tt>clone</tt> method on an object
	 * whose class is <tt>Object</tt> will result in throwing an
	 * exception at run time.
	 *
	 * @return     a clone of this instance.
	 * @exception  CloneNotSupportedException  if the object's class does not
	 *               support the {@code Cloneable} interface. Subclasses
	 *               that override the {@code clone} method can also
	 *               throw this exception to indicate that an instance cannot
	 *               be cloned.
	 * @see java.lang.Cloneable
	 */
	@Override
	protected Object clone() throws CloneNotSupportedException {
		AbstractTreeModel clone = (AbstractTreeModel) super.clone();

		clone.listeners = new javax.swing.event.EventListenerList();

		return clone;
	}

	/**
	 * Adds a listener for the {@code TreeModelEvent}
	 * posted after the tree changes.
	 *
	 * @param   l       the listener to add
	 * @see     #removeTreeModelListener
	 */
	public void addTreeModelListener(javax.swing.event.TreeModelListener l) {
		listeners.add(javax.swing.event.TreeModelListener.class, l);
	}

	/**
	 * Removes a listener previously added with
	 * {@code addTreeModelListener}.
	 *
	 * @see     #addTreeModelListener
	 * @param   l       the listener to remove
	 */
	public void removeTreeModelListener(javax.swing.event.TreeModelListener l) {
		listeners.remove(javax.swing.event.TreeModelListener.class, l);
	}

	/**
	 * Updates all the structure
	 * Collapses the modified node
	 */
	public void updateStructure() {
		fireStructureChanged();
	}

	public void valueForPathChanged(TreePath path, Object newValue) {
		MutableTreeNode   aNode = (MutableTreeNode)path.getLastPathComponent();
        aNode.setUserObject(newValue);
        fireTreeStructureChanged(path);
	}
}